package com.mf.common.spring.context;



import com.mf.common.spring.annotation.Convertable;

import java.lang.reflect.Method;

public final class ConvertableContext<T, V>
{
    private final Class<T> type;
    private final Class<V> valueType;
    private final Method valueMethod;
    private final Method ofMethod;
    private final Object[] zeroParameter = new Object[0];

    ConvertableContext(Class<T> type, Class<V> valueType, Method valueMethod, Method ofMethod)
    {
        this.type = type;
        this.valueType = valueType;
        this.valueMethod = valueMethod;
        this.ofMethod = ofMethod;
    }

    public Class<T> getType() {
        return this.type;
    }

    public Class<V> getValueType() {
        return this.valueType;
    }

    public V value(T source) throws Exception {
        return this.valueType.cast(this.valueMethod.invoke(source, this.zeroParameter));
    }

    public T of(V value) throws Exception {
        return this.type.cast(this.ofMethod.invoke(null, new Object[] { value }));
    }

    public static <T, V> ConvertableContext<T, V> build(String className) throws RuntimeException
    {
        Class convertableClazz;
        try {
            convertableClazz = Class.forName(className);
        } catch (ClassNotFoundException cnfe) {
            throw new RuntimeException("Class not found", cnfe);
        }
        return build(convertableClazz);
    }

    public static <T, V> ConvertableContext<T, V> build(Class<T> type)
            throws RuntimeException
    {
        if (!type.isAnnotationPresent(Convertable.class)) {
            throw new RuntimeException("Class should be annotated by Convertable.");
        }

        Convertable convertable = (Convertable)type.getAnnotation(Convertable.class);
        Method valueMethod;
        try {
            valueMethod = type.getMethod(convertable.valueMethod(), new Class[0]);
        }
        catch (Exception exception) {
            throw new RuntimeException("Failed to obtain method:" + convertable.valueMethod() + "(which should have non-argument).", exception);
        }

        Class valueType = valueMethod.getReturnType();
        Method ofMethod;
        try
        {
            ofMethod = type.getMethod(convertable.ofMethod(), new Class[] { valueType });
        }
        catch (Exception exception) {
            throw new RuntimeException("Failed to obtain method:" + convertable.ofMethod() + "(which should have a(n) " + valueType.getName() + " typed argument)", exception);
        }

        return new ConvertableContext(type, valueType, valueMethod, ofMethod);
    }
}